// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: GM.scala 2482 2009-10-07 16:55:33Z nmueller $
package locutor.model.parsing.grgen

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

/** This object provides a simple interface to the GM parser class.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
object GM extends GMParser {
  def apply(input: String): T = apply("locutor", input)
  
  def apply(input: File): T =
    if(input == null || !(input exists))
      throw new Exception(format("Physical GM file may not be null (''{0}'').", input))
    else apply(input.name, input.content)
  
  def apply(name: String, input: String) =
    if(input == null)
      throw new Exception("Physical GM representation may not be null.")
    else parse(name, input)
  
  def parse(name: String, input: String) = phrase(gm(name))(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new Exception(format("GM parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

/** 
 * Restrictions:
 * - Can't parse, for example, ``const operatorMap:map<dom_AssignmentOperatorKind, string> = { ... }''
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class GMParser extends ExprParser {
  /* Fill in abstract defs*/
  type Tokens = GMLexer
  val lexical: Tokens = new GMLexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "enum",
    "abstract", "const", "node", "edge", "class", "extends",
    "arbitrary", "directed", "undirected", "connect",
    "inherited", "set",
    "boolean", "object"
  ) ++ ExprParser.reserved
  
  lexical.delimiters ++= List(
    "{", "}",
    "->", "--", "<->",
    ";", "="
    // Expression parser delimiters:
    // "[", "]", "(", ")",
    // "!", "?", ":", ",", ".", "::", 
    // "^", "&&", "||", "&", "|",
    // "<<", ">>>", ">>", "+", "-", "*", "/", "%", "~",
    // "<=", "<", "==", "!=", ">=", ">"
  ) ++ ExprParser.delimiters
  
  /* Define the grammar */
  def gm(name: String) = (enum_stmt*) ~ (class_stmt*) ^^ {
    case enums ~ classes =>
      val (ns, es) = classes partition {case _: NodeClass => true case _ => false}
      T(name, enums, ns, es)
  }

  def enum_stmt = ("enum" ~> ident_decl) ~ ("{" ~> repsep(ident_decl ~ opt("=" ~> expr), ",")) <~ "}" ^^ {
    case id ~ items => Enum(id, items map {case k ~ v => EnumItem(k, v)})
  }                                                
  
  def class_stmt = 
    ( opt("abstract") ~ opt("const") ~ node_class ^^ {
      case a ~ c ~ ((id, ex, as)) =>
        NodeClass(
          id,
          ex match {case Some(x) => x case _ => Nil},
          as,
          a match { case Some(_) => true case _ => false},
          c match { case Some(_) => true case _ => false}
        )
    }
    | opt("abstract") ~ opt("const") ~ edge_class ^^ {
      case a ~ c ~ ((dir, id, ex, cs, as)) =>
        EdgeClass(
          dir,
          id,
          ex match {case Some(x) => x case _ => Nil},
          cs match {case Some(x) => x case _ => Nil},
          as,
          a match { case Some(_) => true case _ => false},
          c match { case Some(_) => true case _ => false}
        )
    }
    )
  
  def node_class =
    ( ("node" ~ "class") ~> (ident_decl ~ opt(extends_stmt)) <~ ";"                           ^^ {case id ~ ex      => (id, ex, Nil)}
    | ("node" ~ "class") ~> (ident_decl ~ opt(extends_stmt)) ~ ("{" ~> ((attr_stmt)*) <~ "}") ^^ {case id ~ ex ~ as => (id, ex, as)}
    )
  
  def edge_class = 
    ( opt(direction) ~ (("edge" ~ "class") ~> ident_decl) ~ opt(extends_stmt) ~ (opt(connect_stmt) <~ ";") ^^ {
      case dir ~ id ~ ex ~ cs => (dir, id, ex, cs, Nil)
    }
    | opt(direction) ~ (("edge" ~ "class") ~> ident_decl) ~ opt(extends_stmt) ~ opt(connect_stmt) ~ ("{" ~> ((attr_stmt)*) <~ "}") ^^ {
      case dir ~ id ~ ex ~ cs ~ as => (dir, id, ex, cs, as)
    }
    )

  def direction = "arbitrary" | "undirected" | "directed"
  
  def extends_stmt = "extends" ~> ((ident <~ opt(","))*)
  
  def connect_stmt = "connect" ~> (connect_assertion ~ (("," ~> connect_assertion)*)) ^^ {
    case h ~ t => h :: t
  }

  def connect_assertion =
    ( "inherited"
    | ident ~ opt(range_stmt) ~ ("->" | "<->" | "--") ~ ident ~ opt(range_stmt) ^^ {
      case dom ~ domr ~ d ~ cod ~ codr =>
        dom + {domr match {
          case Some(r) => r
          case None    => ""
        }} + d + cod + {codr match {
          case Some(r) => r
          case None    => ""
        }}  
    }
    )
  
  def range_stmt = "[" ~>
    ( "*" ^^^ {"[*]"}
    | "+" ^^^ {"[+]"}
    | numeric ~ opt(":" ~> ("*" | numeric)) ^^ {
      case l ~ Some(r) => "[" + l + ":" + r + "]"
      case l ~ None    => "[" + l + "]"
    }
    ) <~ "]"
  
  def attr_stmt = 
    ( ident_decl ~ (":" ~> types) ~ opt("=" ~> expr) <~ ";" ^^ {case id ~ t ~ e => (Ident(id.name, Some(t), id.annos), e)}
    | ident_decl ~ ("=" ~> expr <~ opt(";"))                ^^ {case id ~ e     => (Ident(id.name, None, id.annos), Some(e))}
    )
  
  def types = ident | primitives | ("set" ~ "<") ~> primitives <~ ">" ^^ {case p => "set<" + p + ">"}

}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @thanks GrGEN.NET manual for syntax rules (v2.5.x)
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class GMLexer extends StdLexical with RegexParsers {
  override type Elem = Char
  
  override def token = 
    ( string ^^ StringLit
    | number ^^ NumericLit
    | delim
    | id     ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}  
    | EofCh ^^^ EOF    
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  def string  = '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }  
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" |'\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}
